package lenfs.util;

import java.net.SocketAddress;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;

public class XDR {

	public static final int UNIT = 4;

	private Channel channel;
	private final ChannelBuffer buffer;

	private SocketAddress address;

	private void init(final Channel channel, final SocketAddress address) {
		this.channel = channel;
		this.address = address;
	}

	public XDR(final ChannelBuffer buffer, final Channel channel,
			final SocketAddress address) {
		this.buffer = ChannelBuffers.copiedBuffer(buffer);
		init(channel, address);
	}

	public XDR(final int size, final Channel channel,
			final SocketAddress address) {
		int length = size + roundLength(size);
		this.buffer = ChannelBuffers.buffer(length);
		init(channel, address);
	}

	public Channel getChannel() {
		return channel;
	}

	public SocketAddress getAddress() {
		return address;
	}

	public void close() {
		channel.close();
	}

	public void dumpUInt() {
		int startingOffset = buffer.readerIndex();
		buffer.resetReaderIndex();
		for (int i = 0; i < buffer.capacity() / UNIT; ++i)
			System.out.println(getUInt());
		buffer.readerIndex(startingOffset);
	}

	public void dumpLong() {
		int startingOffset = buffer.readerIndex();
		buffer.resetReaderIndex();
		for (int i = 0; i < buffer.capacity() / (2 * UNIT); ++i)
			System.out.println(getLong());
		buffer.readerIndex(startingOffset);
	}

	public void send() {
		ChannelFuture channelFuture = channel.write(buffer, address);
		channelFuture.addListener(new ChannelFutureListener() {
			public void operationComplete(final ChannelFuture future) {
				// Should be equal to channel in XDRPacket
				future.getChannel().disconnect();
			}
		});
	}

	private int roundLength(int original) {
		int r = original & 3;
		if (r == 0)
			return 0;
		return (UNIT - r);
	}

	public String getString(final long length) {
		if (length < 0 || length > buffer.readableBytes())
			return null;
		else if (length == 0)
			return "";
		char str[] = new char[(int) length];
		for (int i = 0; i < length; ++i)
			str[i] = (char) buffer.readByte();
		buffer.readerIndex(buffer.readerIndex() + roundLength((int) length));
		return new String(str);
	}

	public String getString() {
		return getString(getUInt());
	}

	public long getLong() {
		if (buffer.readableBytes() < 2 * UNIT)
			return -1;
		return buffer.readLong();
	}

	public void addLong(final long cValue) {
		buffer.writeLong(cValue);
	}

	public int getInt() {
		if (buffer.readableBytes() < UNIT)
			return Integer.MIN_VALUE;
		return buffer.readInt();
	}

	public long getUInt() {
		if (buffer.readableBytes() < UNIT)
			return -1;
		return buffer.readUnsignedInt();
	}

	public void addUInt(final long cValue) {
		buffer.writeInt((int) cValue);
	}

	public void addUInt(final int cValue) {
		buffer.writeInt(cValue);
	}

	public void addString(final String s) {
		int length = (int) s.length();
		addUInt(length);

		for (int i = 0; i < length; ++i)
			buffer.writeByte(s.charAt(i));
		final int zeros = roundLength(length);
		buffer.writeZero(zeros);
	}

	public void addBytes(final byte[] bytes, final int count) {
		addUInt(count);
		for (int i = 0; i < count; ++i)
			buffer.writeByte(bytes[i]);
		final int zeros = roundLength(count);
		buffer.writeZero(zeros);
	}

	public byte[] getBytes() {
		int length = (int) getUInt();
		if (length <= 0 || length > buffer.readableBytes())
			return null;
		return buffer.readBytes(length).array();
	}

	public void addNullAuth() {
		addUInt(0);
		addUInt(0);
	}

	public void skipAuth() {
		getUInt();
		int authLength = (int) getUInt();
		if (authLength != -1)
			buffer.readerIndex(buffer.readerIndex() + authLength
					+ roundLength(authLength));
	}

	public long getCookie() {
		return this.getUInt();
	}
}
